package lotterysystem.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import jakarta.annotation.Resource;
import lotterysystem.lotterysystem.common.exception.ServiceException;
import lotterysystem.lotterysystem.common.utils.JacksonUtil;
import lotterysystem.lotterysystem.common.utils.MailUtil;
import lotterysystem.lotterysystem.controller.param.DrawPrizeParam;
import lotterysystem.lotterysystem.dao.dataobject.ActivityPrizeDO;
import lotterysystem.lotterysystem.dao.dataobject.WinnerRecordsDO;
import lotterysystem.lotterysystem.dao.mapper.ActivityPrizeMapper;
import lotterysystem.lotterysystem.dao.mapper.WinningRecordsMapper;
import lotterysystem.lotterysystem.service.DrawPrizeService;
import lotterysystem.lotterysystem.service.activitystatus.ActivityStatusManager;
import lotterysystem.lotterysystem.service.dto.ConvertActivityStatusDTO;
import lotterysystem.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import lotterysystem.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import lotterysystem.lotterysystem.service.enums.ActivityStatusEnum;
import lotterysystem.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static lotterysystem.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Resource
    private DrawPrizeService drawPrizeService;

    @Resource
    private ActivityStatusManager activityStatusManager;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private MailUtil mailUtil;

    @Resource
    private ActivityPrizeMapper activityPrizeMapper;

    @Resource
    private WinningRecordsMapper winningRecordsMapper;

    @RabbitHandler
    public void process(Map<String,String> message) {
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息,message:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString,DrawPrizeParam.class);
        // 处理抽奖流程

        try {
            // 校验抽奖请求是否有效
            // 前端发起两个一样的抽奖请求，对于param来说也是两个一样的请求
            // param：最后一个奖项 -》
            //      处理param1：活动完成，奖品完成
            //      处理param2：回滚活动，奖品状态
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }
            // 状态扭转处理
            statusConvert(param);
            // 保存中奖者名单
            List<WinnerRecordsDO> winnerRecordsDOList = drawPrizeService.saveWinnerRecords(param);
            // 通知中奖者（邮箱）
            // 抽奖之后的后续流程，采用异步的方式
            syncExecute(winnerRecordsDOList);

        } catch (ServiceException e) {
            logger.error("处理MQ消息异常！{}:{}",e.getCode(),e.getMessage(),e);
            // 需要保持事务的一致性（回滚），
            rollback(param);
            // 抛出异常:消息重试（解决异常：代码bug，网络问题，服务问题）
            throw e;
        } catch (Exception e) {
            logger.error("处理MQ消息异常！",e);
            // 需要保持事务的一致性（回滚），
            rollback(param);
            // 抛出异常
            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚行为，恢复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1.回滚状态：活动表，人员表，奖品表
        // 状态是否需要回滚
        if (!statusNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要回滚：回滚操作
        rollbackStatus(param);
        // 2.回滚中奖者名单
        // 是否需要回滚
        if (!winnerNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要回滚：回滚操作
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录，删除奖品下的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        // 判断活动中的奖品是否存在中奖者，
        int count = winningRecordsMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count > 0;
    }

    /**
     * 恢复相关状态
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 涉及状态的恢复：使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetActivityUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
        
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        // 判断活动 + 奖品 + 人员 相关状态是否已经扭转（正常思路）
        // 扭转状态时保证了事务一致性，要么都扭转了，要么都没扭转（不包含活动）
        // 只用判断人员或者奖品是否扭转过，就能判断出状态是否全部扭转
        // 不能判断活动是否已经扭转
        // 结论：判断奖品状态是否扭转，就能判断全部状态是否扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());

        // 已经扭转，需要回滚
        return activityPrizeDO.getStatus().
                equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 并发处理抽奖的后续流程
     * @param winnerRecordsDOList 中奖记录表
     */
    private void syncExecute(List<WinnerRecordsDO> winnerRecordsDOList) {
        // 通过线程池 threadPoolTaskExecutor
        // 邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winnerRecordsDOList));
    }

    /**
     * 发送邮件
     * @param winnerRecordsDOList
     */
    private void sendMail(List<WinnerRecordsDO> winnerRecordsDOList) {
        if (CollectionUtils.isEmpty(winnerRecordsDOList)) {
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinnerRecordsDO winnerRecordsDO : winnerRecordsDOList) {
            String context = "Hi，" + winnerRecordsDO.getWinnerName() + "。恭喜你在"
                    + winnerRecordsDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winnerRecordsDO.getPrizeTier()).getMessage()
                    + "：" + winnerRecordsDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winnerRecordsDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winnerRecordsDO.getWinnerEmail(),
                    "中奖通知",context);
        }

    }

    /**
     * 状态扭转
     * @param param
     */

    private void statusConvert(DrawPrizeParam param) {

        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetActivityUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);

    }
}
//    private void statusConvert(DrawPrizeParam param) {
//
//        // 按部就班处理的问题：
//        // 1.活动状态扭转存在依赖性，导致代码维护性差
//        // 2.状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
//        // 3.代码的灵活性，拓展性，维护性极差
//        // 解决方案：设计模式（责任链模式，策略模式）
//
//
//        // 活动 RUNNING   --> COMPLETED ?? 全部奖品抽完后，才改变状态
//        // 奖品 INIT      --> COMPLETED
//        // 人员列表 INIT   --> COMPLETED
//
//        // 1 扭转奖品状态
//        // 查询活动关联奖品信息
//        // 条件判断是否符合扭转奖品状态：判断当前状态是否 不是COMPLETED，如果不是：要扭转
//        // 状态扭转
//
//        // 2 扭转人员状态
//        // 查询活动关联人员信息
//        // 条件判断是否符合扭转人员状态：判断当前状态是否 不是COMPLETED，如果不是：要扭转
//        // 状态扭转
//
//        // 3 扭转活动状态（必须在扭转奖品状态之后）
//        // 查询活动信息
//        // 条件判断是否符合扭转活动状态：
//        // 判断当前状态是否 不是COMPLETED，如果不是，
//        // 且全部奖品抽完之后，才改变状态
//        // 状态扭转
//
//        // 4 更新活动完整信息缓存
//    }
//}
